package org.ws.edu.task1.jimotozka;

import java.util.Stack;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.ws.edu.task1.IConsoleHandler;

public class JimotozkaConsoleHandler implements IConsoleHandler {
    private static final String REGEX_ARRAY = "\\[((.?[1-9]*.?[1-9]*,.?)*(.?[1-9]*.?[1-9]*.?))\\]";
    private static final String REGEX_QUADRATIC = "(.*)x\\^2(.*)x(.*)";
    private static final String REGEX_INCORRECT_ENTER = ".*[^1-9 +-\\^\\/*xX//[//]()!.,].*";

    JimotozkaCalculator calc = new JimotozkaCalculator();

    @Override
    public String handle(String line) {
        String result = null;
        try {
            if (line.equals("exit")) {
                return "exit";
            }
            checkOnCorrectEnter(line);
            arrayHandle(line);
            quadraticEquation(line);
            String polandPresentation = polandNotation(line);
            StringTokenizer st = new StringTokenizer(polandPresentation);
            Stack<Double> stack = new Stack<Double>();

            while (st.hasMoreTokens()) {
                String tokens = st.nextToken();
                if (Character.isDigit(tokens.charAt(0))) {
                    ifTokenIsDigit(stack, tokens);
                }

                if ((tokens.charAt(0) == '+') || (tokens.charAt(0) == '-') || (tokens.charAt(0) == '*')
                        || (tokens.charAt(0) == '/') || (tokens.charAt(0) == '^')) {
                    ifTokenIsOperand(stack, tokens);
                }

                if (tokens.charAt(0) == '!') {
                    ifTokenIsFactorial(stack);
                }

            }
            result = "" + stack.pop();

        } catch (IncorrectEntryException e) {
            result = "Incorrect Entry";
        } catch (IllegalArgumentException e) {
            System.err.println("Argument of factorial must be positive and integer");
        } catch (Exception e) {

        }
        return result;

    }

    private void ifTokenIsDigit(Stack<Double> stack, String tokens) {
        stack.push(Double.parseDouble(tokens));
    }

    private void ifTokenIsOperand(Stack<Double> stack, String tokens) {
        stack.push(calc.calculate(stack.pop(), stack.pop(), tokens));
    }

    private void ifTokenIsFactorial(Stack<Double> stack) {
        throwFactorialException(stack.peek());
        stack.push((double) calc.factorial((int) ((double) stack.pop())));
    }

    private String polandNotation(String expression) {

        String rezultNotation = new String();
        Stack stack = new Stack();
        Stack outString = new Stack();
        for (int i = 0; i < expression.length(); i++) {
            int m = i;
            if (expression.charAt(i) == ')') {
                while (String.valueOf(stack.peek()).charAt(0) != '(') {
                    outString.push(" " + stack.pop());
                }
                stack.pop();
                continue;
            }
            if (expression.charAt(i) == '(') {
                stack.push('(');
                continue;
            }

            if (Character.isDigit(expression.charAt(i))) {
                String temp = String.valueOf(expression.charAt(i));
                for (int k = ++m; k <= expression.length() - 1; k++) {
                    if (Character.isDigit(expression.charAt(k)) || expression.charAt(k) == '.') {
                        temp += String.valueOf(expression.charAt(k));
                        i++;
                    } else
                        break;
                }
                outString.push(" " + temp);
                continue;
            }

            if ((expression.charAt(i) == '+') || (expression.charAt(i) == '-') || (expression.charAt(i) == '*')
                    || (expression.charAt(i) == '/') || (expression.charAt(i) == '!') || (expression.charAt(i) == '^')) {
                if (stack.size() == 0) {
                    stack.push(expression.charAt(i));
                } else if (prior(expression.charAt(i)) > prior(String.valueOf(stack.peek()).charAt(0))) {
                    stack.push(expression.charAt(i));
                } else {
                    while ((stack.size() != 0)
                            && (prior(String.valueOf(stack.peek()).charAt(0))) >= prior(expression.charAt(i))) {
                        outString.push(" " + stack.pop());
                    }
                    stack.push(expression.charAt(i));
                }
                continue;
            }
        }
        for (int l = 0; l < outString.size(); l++) {
            rezultNotation = rezultNotation + String.valueOf(outString.get(l));
        }
        while (!stack.isEmpty()) {
            rezultNotation += " " + String.valueOf(stack.pop());
        }
        return rezultNotation;
    }

    private int prior(char c) {
        switch (c) {

            case '!':
                return 5;
            case '^':
                return 4;
            case '*':
                return 3;
            case '/':
                return 3;
            case '-':
                return 2;
            case '+':
                return 2;
            case '(':
                return 1;
        }
        return 0;
    }

    private void checkOnCorrectEnter(String s) throws IncorrectEntryException {
        Pattern pattern = null;
        if ((s.length() <= 1) || pattern.matches(REGEX_INCORRECT_ENTER, s)) {
            throw new IncorrectEntryException();
        }
    }

    private void throwFactorialException(double n) throws IllegalArgumentException {
        int integer = (int) n;
        boolean b = (n - integer) == 0;
        if (!b || n < 0) {
            throw new IllegalArgumentException();
        }
    }

    private void arrayHandle(String line) throws Exception {
        Pattern pattern = Pattern.compile(REGEX_ARRAY);
        Matcher matcher = pattern.matcher(line);
        if (matcher.find()) {
            String[] elementsOfArray = matcher.group(1).split(",");
            Double[] doubleElements = new Double[elementsOfArray.length];
            for (int i = 0; i < elementsOfArray.length; i++) {
                doubleElements[i] = Double.parseDouble(elementsOfArray[i]);
            }
            sort(doubleElements);
            System.out.println("Минимальное значение: " + doubleElements[0]);
            System.out.println("Максимальное значение: " + doubleElements[doubleElements.length - 1]);
            System.out.println("Отсортированный массив: ");
            for (Double d : doubleElements) {
                System.out.print(d + " ");
            }
            System.out.println("\n");
            throw new Exception();
        }

    }

    private void change(Double[] array, int i, int j) {
        double t = array[i];
        array[i] = array[j];
        array[j] = t;
    }

    private void sort(Double[] array) {
        for (int i = array.length - 1; i >= 0; i--) {
            for (int k = 0; k < i; k++) {
                if (array[k] > array[k + 1])
                    change(array, k, k + 1);
            }
        }
    }

    private void quadraticEquation(String line) throws Exception {
        Double a = null;
        Double b = null;
        Double c = null;

        Pattern pattern = Pattern.compile(REGEX_QUADRATIC);
        Matcher matcher = pattern.matcher(line);
        if (matcher.find()) {
            a = Double.parseDouble(matcher.group(1));
            b = Double.parseDouble(matcher.group(2));
            c = Double.parseDouble(matcher.group(3));
            Double discriminant = calc.pow(b, 2) - 4 * a * c;
            if (discriminant < 0) {
                System.out.println("Уравнение не имеет корней");
            } else if (discriminant == 0) {
                System.out.println("Уравнение имеет один корень: " + (-b / 2 * a));
            } else if (discriminant > 0) {
                System.out.println("Уравнение имеет два корня: \n" + ((-b + calc.pow(discriminant, 0.5)) / 2 * a)
                        + "\n" + ((-b - calc.pow(discriminant, 0.5)) / 2 * a));
            }
            throw new Exception();
        }

    }
}
